<?php
/**
 * Copyright (c) [2019] [吴跃忠]
 * [selibra] is licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 * http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 */

namespace Selibra\Model;

use PDOStatement;
use Selibra\Tools\Console;
use Selibra\Di\DI;
use Selibra\Model\Build\ConditionBuilder;
use Selibra\Model\Build\ConnectBuilder;
use Selibra\Model\Build\LimitBuilder;
use Selibra\Model\Build\SqlBuilder;
use Selibra\Model\Exception\DbException;
use Selibra\ORM\EntityCollector;
use Selibra\ORM\TableCollector;
use Selibra\ORM\TableManager;

class SFModel
{

    /**
     * @var string
     */
    protected ?string $table = null;


    /**
     * @var ConnectBuilder
     */
    private ConnectBuilder $connectBuilder;


    /**
     * @var string
     */
    private $entity;


    /**
     * @var object
     */
    private $entityIns;


    /**
     * @var EntityProxy
     */
    private EntityProxy $entityProxy;


    /**
     * @var SqlBuilder
     */
    private $sqlBuilder;


    public function __construct(ConnectBuilder $connectBuilder = null)
    {
        if (!empty($connectBuilder)) {
            // 获取
            $this->connectBuilder = $connectBuilder;
        }
        $this->__init();
    }


    /**
     * 模型初始化
     */
    protected function __init()
    {
    }


    /**
     * @param string $sql
     * @return mixed
     */
    public function query(string $sql, bool $notClearQueryConditions = false)
    {
        if (!$notClearQueryConditions) {
            $this->setCondition(null);
        }
        /** @var PDOStatement $querySta */
        $querySta = $this->connectBuilder()->query($sql);
        if (empty($querySta)) {
            return [];
        }
        $result = $querySta->fetchAll(\PDO::FETCH_NAMED);
        return $result;
    }


    /**
     * 插入
     * @param array|object $params
     * @return bool|array
     * @throws DbException
     */
    public function insert($params = null)
    {
        if (empty($params)) {
            $params = $this->getEntityProxy()->toPropertyArrayData();
        } elseif (is_object($params)) {
            $this->setEntity($params);
            $params = $this->getEntityProxy()->toPropertyArrayData();
        }
        // 组合
        $fields = implode(',', array_map(function ($item) {
            return '`' . $item . '`';
        }, array_keys($params)));
        $preValues = implode(',', array_fill(0, count($params), '?'));
        $values = array_values($params);
        $sql = "INSERT INTO `{$this->getTable()}` ({$fields}) VALUES ({$preValues})";
        $sta = $this->connectBuilder()->prepare($sql);
        if ($sta === false) {
            throw new DbException($this->connectBuilder()->getError());
        }
        $res = $sta->execute($values);
        if (!empty($sta->errorCode()) && $sta->errorCode() != '00000') {
            throw new DbException($sta->errorInfo()[2]);
        }
        return $res;
    }


    /**
     * 增加数据
     * @param array|object $params
     * @return bool|array
     * @throws DbException
     */
    public function setInc($field, $value = 1)
    {
        $where = $this->getCondition()->getConditionSql();
        $sql = "UPDATE `{$this->getTable()}` SET `{$field}`=`{$field}` + {$value} WHERE {$where}";
        $sta = $this->connectBuilder()->prepare($sql);
        if ($sta === false) {
            throw new DbException($this->connectBuilder()->getError());
        }
        $res = $sta->execute();
        if (!empty($sta->errorCode()) && $sta->errorCode() != '00000') {
            throw new DbException($sta->errorInfo()[2]);
        }
        return $res;
    }


    /**
     * 减少数据
     * @param array|object $params
     * @return bool|array
     * @throws DbException
     */
    public function setDec($field, $value = 1)
    {
        $where = $this->getCondition()->getConditionSql();
        $sql = "UPDATE `{$this->getTable()}` SET `{$field}`=`{$field}` - {$value} WHERE {$where}";
        $sta = $this->connectBuilder()->prepare($sql);
        if ($sta === false) {
            throw new DbException($this->connectBuilder()->getError());
        }
        $res = $sta->execute();
        if (!empty($sta->errorCode()) && $sta->errorCode() != '00000') {
            throw new DbException($sta->errorInfo()[2]);
        }
        return $res;
    }


    /**
     * 更新数据
     * @param array|SFEntity $params
     * @return bool|array
     * @throws
     */
    protected function update($params = null)
    {
        if (empty($params)) {
            if (empty($this->entityProxy)) {
                throw new DbException("无修改数据");
            }
            $params = $this->getEntityProxy()->toPropertyArrayData();
        } elseif (is_object($params)) {
            $this->setEntity($params);
            $params = $this->getEntityProxy()->toPropertyArrayData();
        }
        // 组合
        $fieldsList = array_map(function ($item) {
            return '`' . $item . '` = ?';
        }, array_keys($params));
        $fields = implode(',', $fieldsList);
        $values = array_values($params);
        $where = $this->getCondition()->getConditionSql();
        $sql = "UPDATE `{$this->getTable()}` SET {$fields} WHERE {$where}";
        $sta = $this->prepare($sql);
        if ($sta === false) {
            Console::log($this->getError());
            return false;
        }
        $res = $sta->execute($values);
        if (!empty($sta->errno)) {
            throw new DbException($sta->error);
        }
        return $res;
    }


    /**
     * @param $sql
     * @return \Swoole\Coroutine\MySQL\Statement
     */
    public function prepare($sql, bool $notClearQueryConditions = false)
    {
        $sta = $this->connectBuilder()->prepare($sql);
        // 清除掉条件
        if (!$notClearQueryConditions) {
            $this->setCondition(null);
        }
        return $sta;
    }


    /**
     * @param $params
     * @return mixed
     * @throws
     */
    public function insertGetId($params = null)
    {
        $insertResult = $this->insert($params);
        if ($insertResult === false) {
            $error = $this->getError();
            if ($error[0] !== '00000') {
                Console::log($this->getError());
                throw new DbException($error[0] . ':' . $error[1] . ':' . $error[2]);
            }
        }
        $result = $this->query('SELECT LAST_INSERT_ID();');
        return $result[0]['LAST_INSERT_ID()'];
    }


    /**
     * @return mixed
     */
    public function delete()
    {
        $sql = $this->getSqlBuilder()->getDeleteSql();
        $sta = $this->prepare($sql);
        if ($sta === false) {
            Console::log($this->getError());
            return false;
        }
        $res = $sta->execute([]);
        if (!empty($sta->errno)) {
            throw new DbException($sta->error);
        }
        return $res;
    }


    /**
     * @param int|null $id
     * @param bool $toEntity
     * @return SFEntity|null|mixed
     */
    public function get(int $id = null, bool $toEntity = true, bool $notClearQueryConditions = false)
    {
        if (!empty($id)) {
            $sql = 'SELECT * FROM `' . $this->getTable() . '` WHERE `id` = ' . intval($id) . ' LIMIT 1;';
        } else {
            $sql = $this->getSqlBuilder()->getGetSql();
        }
        $result = $this->query($sql, $notClearQueryConditions);
        if (!empty($result)) {
            if ($toEntity && $this->hasEntity()) {
                $entity = $this->toEntity($result[0]);
            } else {
                $entity = $result[0];
            }
            return $entity;
        } else {
            return null;
        }
    }


    /**
     * @param $group
     * @return $this
     */
    public function group($group)
    {
        $this->getSqlBuilder()->setGroup($group);
        return $this;
    }


    /**
     * @param $field
     * @return $this
     */
    public function field($field)
    {
        $this->getSqlBuilder()->setField($field);
        return $this;
    }


    /**
     * @return array
     */
    public function select(bool $notClearQueryConditions = false)
    {
        $sql = $this->getSqlBuilder()->getSelectSql();
        $result = $this->query($sql, $notClearQueryConditions);
        if (empty($result)) {
            return [];
        }
        if ($this->hasEntity()) {
            $resultList = [];
            foreach ($result as $key => $item) {
                $resultList[] = $this->toEntity($item);
            }
            return $resultList;
        }
        return $result;
    }


    /**
     * @param string $field
     * @return array|null
     */
    public function column($field)
    {
        $this->field($field);
        $sql = $this->getSqlBuilder()->getSelectSql();
        $data = $this->query($sql);
        $result = [];
        foreach ($data as $key => $item) {
            array_push($result, $item[$field]);
        }
        return $result;
    }


    /**
     * @param ConditionBuilder $condition
     * @return $this
     */
    public function setCondition($condition)
    {
        $this->getSqlBuilder()->setConditionBuilder($condition);
        return $this;
    }


    /**
     * 获取查询条件
     * @return ConditionBuilder
     */
    public function getCondition()
    {
        if (empty($this->getSqlBuilder()->getConditionBuilder())) {
            $this->getSqlBuilder()->setConditionBuilder(new ConditionBuilder());
        }
        return $this->getSqlBuilder()->getConditionBuilder();
    }


    /**
     * @param $start
     * @param $size
     * @return $this
     */
    public function setLimit($start, $size)
    {
        $this->getSqlBuilder()->setLimitBuilder(new LimitBuilder($start, $size));
        return $this;
    }


    /**
     * 获取表名称
     * @return string
     */
    public function getTable()
    {
        if (!isset($this->table)) {
            $tableManager = new TableManager($this);
            $this->table = $tableManager->getDaoTable();
        }
        return $this->table;
    }


    /**
     * @return object
     */
    public function getEntity()
    {
        if (empty($this->entityIns)) {
            $entity = $this->getEntityClass();
            $this->entityIns = new $entity();
        }
        return $this->entityIns;
    }


    /**
     * @return EntityProxy
     */
    public function getEntityProxy()
    {
        if (empty($this->entityProxy)) {
            $this->entityProxy = new EntityProxy($this->getEntity());
        }
        return $this->entityProxy;
    }


    public function getEntityClass()
    {
        if (!isset($this->entity)) {
            $this->entity = EntityCollector::get(get_class($this));
        }
        return $this->entity;
    }


    /**
     * @param object $entity
     * @return $this
     */
    public function setEntity(object $entity)
    {
        $this->entityIns = $entity;
        $this->entityProxy = new EntityProxy($this->getEntity());
        return $this;
    }


    /**
     * @param $result
     * @return object
     */
    public function toEntity($result)
    {
        if (empty($result)) {
            return null;
        }
        $entityClass = $this->getEntityClass();
        $item = new EntityProxy(new $entityClass());
        $vars = $this->selectProps();
        foreach ($vars as $var) {
            $item->setProperty($var, $result[$var]);
        }
        return $item->getEntity();
    }


    /**
     * @return bool
     */
    public function hasEntity(): bool
    {
        $entityClass = $this->getEntityClass();
        return !empty($entityClass);
    }


    /**
     * 获取属性
     * @return array
     */
    public function selectProps()
    {
        $vars = get_class_vars($this->getEntityClass());
        $props = [];
        foreach ($vars as $var => $value) {
            array_push($props, $var);
        }
        return $props;
    }


    /**
     * @return string
     */
    public function getError()
    {
        return $this->connectBuilder()->getError();
    }


    /**
     * @return SqlBuilder
     */
    public function getSqlBuilder(): SqlBuilder
    {
        if (empty($this->sqlBuilder)) {
            $this->sqlBuilder = new SqlBuilder($this);
            $this->sqlBuilder->setTable($this->getTable());
        }
        return $this->sqlBuilder;
    }


    protected function connectBuilder()
    {
        if (!isset($this->connectBuilder)) {
            $this->connectBuilder = new ConnectBuilder();
        }
        return $this->connectBuilder;
    }


}
